package org.rency.ues.client.impl;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.rency.common.utils.domain.RespBody;
import org.rency.common.utils.security.MD5Utils;
import org.rency.common.utils.security.X509CertUtils;
import org.rency.ues.client.UesClient;
import org.rency.ues.common.UesDict;
import org.rency.ues.common.UesException;
import org.rency.ues.common.UesUtils;
import org.rency.ues.common.enums.EncryptType;
import org.rency.ues.common.model.EncryptData;
import org.rency.ues.common.model.EncryptItem;
import org.rency.ues.common.model.TransferBody;
import org.rency.ues.common.model.UesCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;

import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * UES加密客户端
 *
 * @author: Created by user_rcy@163.com on 2016/8/1.
 * @version: V1.0.0
 * @since JDK 1.8
 */
public class UesClientImpl implements UesClient {

    private static final Logger logger = LoggerFactory.getLogger(UesClientImpl.class);

    private RestTemplate restTemplate;

    private String encryptRemoteAddress;

    private String decryptRemoteAddress;

    private String certificationRemoteAddress;

    private String userName;

    private String password;

    @Override
    public String encrypt(String plainData) throws UesException {
        return this.encrypt(plainData,null);
    }

    @Override
    public String encrypt(String plainData, String serialNo) throws UesException {
        return this.encrypt(plainData,EncryptType.RSA,serialNo);
    }

    @Override
    public String encrypt(String plainData, EncryptType encryptType, String serialNo) throws UesException {
        this.encryptValidate(plainData,encryptType,serialNo);
        X509Certificate x509Certificate = this.getX509Certification(serialNo);
        TransferBody request = new TransferBody();
        request.setCredentials(this.getCredentials());
        request.setSerialNo(x509Certificate.getSerialNumber().toString());
        request.setEncryptType(encryptType);
        EncryptItem enItem = this.generateEncryptItems(plainData,x509Certificate);
        request.addEnItem(enItem);
        RespBody response = restTemplate.postForObject(encryptRemoteAddress,request,RespBody.class,new HashMap<String, Object>());
        if(response == null || !response.isSuccess()){
            logger.info("远程加密失败,{}.",response);
            throw new UesException("远程加密失败."+response);
        }
        String ticket = (String)response.getData().get(enItem.getEncryptedKey());
        if(StringUtils.isBlank(ticket)){
            logger.info("远程加密失败,密钥未返回,{}.",response);
            throw new UesException("远程加密失败,密钥未返回.");
        }
        return ticket;
    }

    @Override
    public String decrypt(String ticket) throws UesException {
        TransferBody request = new TransferBody();
        request.setCredentials(this.getCredentials());
        request.addTicket(ticket);
        RespBody response = restTemplate.postForObject(decryptRemoteAddress,request,RespBody.class,new HashMap<String, Object>());
        if(response == null || !response.isSuccess()){
            logger.info("远程解密失败,{}.",response);
            throw new UesException("远程解密失败."+response);
        }

        LinkedHashMap<String,String> enDataMap = (LinkedHashMap<String, String>) response.getData().get(ticket);
        String encryptedKey = enDataMap.get("encryptedKey");
        String encryptedData = enDataMap.get("encryptedData");
        String serialNo = enDataMap.get("serialNo");
        String encryptType = enDataMap.get("encryptType");
        String privateKeyString = (String) response.getData().get(UesDict.REST_PARAMETER_KEY_PRIVATE);
        if(StringUtils.isBlank(encryptedKey) || StringUtils.isBlank(encryptedData) ||
                StringUtils.isBlank(serialNo)|| StringUtils.isBlank(encryptType) ||
                StringUtils.isBlank(privateKeyString) || !EncryptType.isValid(encryptType)){
            logger.info("远程解密失败,返回参数为空,{}.",response);
            throw new UesException("远程解密失败,返回参数为空.");
        }

        EncryptData enData = new EncryptData();
        enData.setEncryptType(EncryptType.get(encryptType));
        enData.setEncryptedKey(encryptedKey);
        enData.setEncryptedData(encryptedData);
        enData.setSerialNo(serialNo);
        try {
            PrivateKey privateKey= X509CertUtils.getPrivateKeyFromBase64String(privateKeyString);
            String plainData = UesUtils.getPlainData(enData, this.getX509Certification(enData.getSerialNo()), privateKey);
            if (StringUtils.isBlank(plainData)) {
                logger.info("远程解密失败,密文解析为空,{}.", enData);
                throw new UesException("远程解密失败,密文解析为空.");
            }
            return plainData;
        }catch (Exception e){
            logger.error("远程解密失败,密文解析异常.",e);
            throw new UesException("远程解密失败,密文解析异常.");
        }
    }

    @Override
    public Map<String, String> encrypt(List<String> plainDatas) throws UesException {
        return this.encrypt(plainDatas,null);
    }

    @Override
    public Map<String, String> encrypt(List<String> plainDatas, String serialNo) throws UesException {
        return this.encrypt(plainDatas,EncryptType.RSA,serialNo);
    }

    @Override
    public Map<String, String> encrypt(List<String> plainDatas, EncryptType encryptType, String serialNo) throws UesException {
        this.encryptValidate(plainDatas,encryptType,serialNo);
        X509Certificate x509Certificate = this.getX509Certification(serialNo);
        Map<String, String> carrier = new HashMap<String, String>();
        TransferBody request = new TransferBody();
        request.setCredentials(this.getCredentials());
        request.setSerialNo(x509Certificate.getSerialNumber().toString());
        request.setEncryptType(encryptType);
        Map<String, EncryptItem> enItemMap = this.generateEncryptItems(plainDatas,x509Certificate);
        for(Map.Entry<String, EncryptItem> entry : enItemMap.entrySet()){
            request.addEnItem(entry.getValue());
            carrier.put(entry.getValue().getEncryptedKey(),entry.getKey());
        }

        RespBody response = restTemplate.postForObject(encryptRemoteAddress,request,RespBody.class,new HashMap<String, Object>());
        if(response == null || !response.isSuccess()){
            logger.info("远程加密失败,{}.",response);
            throw new UesException("远程加密失败."+response);
        }
        Map<String, String> resultMap = new HashMap<String, String>();
        for(EncryptItem enItem : request.getEnItems()){
            String ticket = (String)response.getData().get(enItem.getEncryptedKey());
            if(StringUtils.isBlank(ticket)){
                logger.info("远程加密失败,密钥未返回,{}.",response);
                throw new UesException("远程加密失败,密钥未返回.");
            }
            resultMap.put(carrier.get(enItem.getEncryptedKey()),ticket);
        }
        return resultMap;
    }

    @Override
    public Map<String, String> decrypt(List<String> tickets) throws UesException {
        Map<String, String> resultMap = new HashMap<String, String>();
        TransferBody request = new TransferBody();
        request.setCredentials(this.getCredentials());
        request.setTickets(tickets);
        RespBody response = restTemplate.postForObject(decryptRemoteAddress,request,RespBody.class,new HashMap<String, Object>());
        if(response == null || !response.isSuccess()){
            logger.info("远程批量解密失败,{}.",response);
            throw new UesException("远程批量解密失败."+response);
        }
        for(String ticket : tickets){
            LinkedHashMap<String,String> enDataMap = (LinkedHashMap<String, String>) response.getData().get(ticket);
            String encryptedKey = enDataMap.get("encryptedKey");
            String encryptedData = enDataMap.get("encryptedData");
            String serialNo = enDataMap.get("serialNo");
            String encryptType = enDataMap.get("encryptType");
            String privateKeyString = (String) response.getData().get(UesDict.REST_PARAMETER_KEY_PRIVATE);
            if(StringUtils.isBlank(encryptedKey) || StringUtils.isBlank(encryptedData) ||
                    StringUtils.isBlank(serialNo)|| StringUtils.isBlank(encryptType) ||
                    StringUtils.isBlank(privateKeyString) || !EncryptType.isValid(encryptType)){
                logger.info("远程批量解密失败,返回参数为空,{}.",response);
                throw new UesException("远程批量解密失败,返回参数为空.");
            }

            EncryptData enData = new EncryptData();
            enData.setEncryptType(EncryptType.get(encryptType));
            enData.setEncryptedKey(encryptedKey);
            enData.setEncryptedData(encryptedData);
            enData.setSerialNo(serialNo);
            try {
                PrivateKey privateKey= X509CertUtils.getPrivateKeyFromBase64String(privateKeyString);
                String plainData = UesUtils.getPlainData(enData, this.getX509Certification(enData.getSerialNo()), privateKey);
                if (StringUtils.isBlank(plainData)) {
                    logger.info("远程批量解密失败,密文解析为空,{}.", enData);
                    throw new UesException("远程解密失败,密文解析为空.");
                }
                resultMap.put(ticket,plainData);
            }catch (Exception e){
                logger.error("远程批量解密失败,密文解析异常.",e);
                throw new UesException("远程批量解密失败,密文解析异常.");
            }
        }
        return resultMap;
    }

    private EncryptItem generateEncryptItems(String plainData, X509Certificate x509Certificate) throws UesException {
        try {
            EncryptData enData = UesUtils.generateEncryptData(plainData, EncryptType.RSA, x509Certificate);
            if (enData == null) {
                throw new UesException("生成待加密数据密文返回空");
            }
            EncryptItem enItem = new EncryptItem();
            enItem.setEncryptedKey(enData.getEncryptedKey());
            enItem.setEncryptedData(enData.getEncryptedData());
            return enItem;
        }catch (Exception e){
            logger.error("生成密文数据异常,{}.",e);
            throw new UesException("生成密文数据异常.");
        }
    }

    private Map<String, EncryptItem> generateEncryptItems(List<String> plainDatas, X509Certificate x509Certificate) throws UesException {
        Map<String, EncryptItem> resultMap = new HashMap<String, EncryptItem>();
        try {
            for (String plainData : plainDatas) {
                EncryptData enData = UesUtils.generateEncryptData(plainData, EncryptType.RSA, x509Certificate);
                if (enData == null) {
                    throw new UesException("生成待加密数据密文返回空");
                }
                EncryptItem enItem = new EncryptItem();
                enItem.setEncryptedKey(enData.getEncryptedKey());
                enItem.setEncryptedData(enData.getEncryptedData());
                resultMap.put(plainData,enItem);
            }
            return resultMap;
        }catch (Exception e){
            logger.error("生成密文数据异常,{}.",e);
            throw new UesException("生成密文数据异常.");
        }
    }

    /**
     * 加密参数校验
     * @param plainData
     * @param encryptType
     * @param serialNo
     */
    private void encryptValidate(String plainData, EncryptType encryptType, String serialNo){
        Assert.notNull(plainData,"待加密数据不能为空");
        Assert.notNull(encryptType,"加密方式不能为空");
    }

    /**
     * 加密参数校验
     * @param plainDatas
     * @param encryptType
     * @param serialNo
     */
    private void encryptValidate(List<String> plainDatas, EncryptType encryptType, String serialNo){
        Assert.isTrue(CollectionUtils.isNotEmpty(plainDatas),"待加密数据不能为空");
        Assert.notNull(encryptType,"加密方式不能为空");
    }

    /**
     * 获取证书
     * @param serialNo
     * @return
     */
    private X509Certificate getX509Certification(String serialNo){
        TransferBody request = new TransferBody();
        request.setCredentials(this.getCredentials());
        request.setSerialNo(serialNo);
        RespBody result = restTemplate.postForObject(certificationRemoteAddress,request,RespBody.class,new HashMap<String, Object>());
        if(result == null || !result.isSuccess()){
            logger.info("远程获取证书失败,{}.",result);
            throw new RuntimeException("远程获取证书失败."+result);
        }
        String certBase = (String)result.getData().get(UesDict.REST_PARAMETER_KEY_CERTIFICATION);
        if(StringUtils.isBlank(certBase)){
            logger.info("远程获取证书失败,证书内容为空,{}.",result);
            throw new RuntimeException("远程获取证书失败."+result);
        }
        X509Certificate x509Certification = null;
        try {
            x509Certification = X509CertUtils.getX509CertFromBase64Cert(certBase);
            if(x509Certification == null){
                throw new CertificateException("转换证书失败,证书为空.");
            }
        } catch (CertificateException e) {
            logger.error("转换证书异常,{}.",serialNo,e);
            throw new RuntimeException("远程获取证书失败,转换证书异常."+serialNo);
        }
        return x509Certification;
    }

    private UesCredentials getCredentials(){
        UesCredentials domain = new UesCredentials();
        domain.setUserName(MD5Utils.MD5(this.userName));
        domain.setPassword(MD5Utils.MD5(this.password));
        return domain;
    }

    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public void setEncryptRemoteAddress(String encryptRemoteAddress) {
        this.encryptRemoteAddress = encryptRemoteAddress;
    }

    public void setDecryptRemoteAddress(String decryptRemoteAddress) {
        this.decryptRemoteAddress = decryptRemoteAddress;
    }

    public void setCertificationRemoteAddress(String certificationRemoteAddress) {
        this.certificationRemoteAddress = certificationRemoteAddress;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
